package me.zhengjie.modules.quartz.service.impl

import cn.hutool.core.util.IdUtil
import cn.hutool.core.util.StrUtil
import jakarta.persistence.criteria.CriteriaBuilder
import jakarta.persistence.criteria.CriteriaQuery
import jakarta.persistence.criteria.Root
import jakarta.servlet.http.HttpServletResponse
import me.zhengjie.common.exception.BadRequestException
import me.zhengjie.common.utils.*
import me.zhengjie.common.utils.FileUtil.downloadExcel
import me.zhengjie.modules.quartz.domain.QuartzJob
import me.zhengjie.modules.quartz.domain.QuartzLog
import me.zhengjie.modules.quartz.repository.QuartzJobRepository
import me.zhengjie.modules.quartz.repository.QuartzLogRepository
import me.zhengjie.modules.quartz.service.QuartzJobService
import me.zhengjie.modules.quartz.service.dto.JobQueryCriteria
import me.zhengjie.modules.quartz.utils.QuartzManage
import org.quartz.*
import org.springframework.cache.annotation.CacheConfig
import org.springframework.cache.annotation.CacheEvict
import org.springframework.cache.annotation.Cacheable
import org.springframework.data.domain.Pageable
import org.springframework.data.jpa.domain.Specification
import org.springframework.scheduling.annotation.Async
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional
import java.io.IOException

/**
 * @author Kuki Wu
 * @date 2019-01-07
 */
@Service(value = "quartzJobService")
@CacheConfig(cacheNames = ["quartzJob"])
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = [Exception::class])
class QuartzJobServiceImpl(
    private val quartzJobRepository: QuartzJobRepository,
    private val quartzLogRepository: QuartzLogRepository,
    private val quartzManage: QuartzManage,
    private val redisUtils: RedisUtil? = null
) : QuartzJobService {
    @Cacheable(key = "'queryall_criteria_pageable'")
    override fun queryAll(criteria: JobQueryCriteria?, pageable: Pageable?): PageResult<QuartzJob?>? {
        return PageUtil.toPage(quartzJobRepository.findAll(Specification { root: Root<QuartzJob?>, criteriaQuery: CriteriaQuery<*>?, criteriaBuilder: CriteriaBuilder ->
            QueryHelp.getPredicate(
                root!!,
                criteria,
                criteriaBuilder!!
            )
        }, pageable!!)) as PageResult<QuartzJob?>
    }

    override fun queryAllLog(criteria: JobQueryCriteria?, pageable: Pageable?): PageResult<QuartzLog?>? {
        return PageUtil.toPage(quartzLogRepository.findAll(Specification { root: Root<QuartzLog?>, criteriaQuery: CriteriaQuery<*>?, criteriaBuilder: CriteriaBuilder ->
            QueryHelp.getPredicate(
                root!!,
                criteria,
                criteriaBuilder!!
            )
        }, pageable!!)) as PageResult<QuartzLog?>
    }

    @Cacheable(key = "'queryall_criteria'")
    override fun queryAll(criteria: JobQueryCriteria?): MutableList<QuartzJob?>? {
        return quartzJobRepository.findAll { root: Root<QuartzJob?>, criteriaQuery: CriteriaQuery<*>?, criteriaBuilder: CriteriaBuilder ->
            QueryHelp.getPredicate(
                root,
                criteria,
                criteriaBuilder
            )
        }
    }

    override fun queryAllLog(criteria: JobQueryCriteria?): MutableList<QuartzLog?>? {
        return quartzLogRepository.findAll { root: Root<QuartzLog?>, criteriaQuery: CriteriaQuery<*>?, criteriaBuilder: CriteriaBuilder ->
            QueryHelp.getPredicate(
                root,
                criteria,
                criteriaBuilder
            )
        }
    }

    @Cacheable(key = "'findbyid'+#p0")
    override fun findById(id: Long?): QuartzJob? {
        val quartzJob = quartzJobRepository.findById(id!!).orElseGet { QuartzJob() }!!
        ValidationUtil.isNull(quartzJob.id, "QuartzJob", "id", id!!)
        return quartzJob
    }

    @Cacheable(key = "'findbybean_name_params_beanname:'+#p0+'_deviceid:'+#p1")
    fun findByBeanNameAndParam(beanName: String?, deviceId: String?): QuartzJob? {
        //ValidationUtil.isNull(quartzJob.getParams(),"QuartzJob","params",deviceId);
        return quartzJobRepository.findByBeanNameAndParam(beanName, deviceId)
    }

    @CacheEvict(allEntries = true)
    @Transactional(rollbackFor = [Exception::class])
    override fun create(resources: QuartzJob?) {
        var resources = resources
        if (!CronExpression.isValidExpression(resources!!.cronExpression)) {
            throw BadRequestException("cron表达式格式错误")
        }
        resources = quartzJobRepository.save(resources)
        quartzManage.addJob(resources)
        //return resources
    }

    @CacheEvict(allEntries = true)
    @Transactional(rollbackFor = [Exception::class])
    override fun update(resources: QuartzJob?) {
        var resources = resources
        if (resources!!.id == 1L) {
            throw BadRequestException("该任务不可操作")
        }
        if (!CronExpression.isValidExpression(resources.cronExpression)) {
            throw BadRequestException("cron表达式格式错误")
        }
        resources = quartzJobRepository.save(resources)
        quartzManage.updateJobCron(resources)
    }

    @CacheEvict(allEntries = true)
    override fun updateIsPause(quartzJob: QuartzJob?) {
        if (quartzJob!!.id == 1L) {
            throw BadRequestException("该任务不可操作")
        }
        if (quartzJob.isPause) {
            quartzManage.resumeJob(quartzJob)
            quartzJob.isPause = false
        } else {
            quartzManage.pauseJob(quartzJob)
            quartzJob.isPause = true
        }
        quartzJobRepository.save(quartzJob)
    }

    @CacheEvict(allEntries = true)
    @Transactional(rollbackFor = [Exception::class])
    fun delete(quartzJob: QuartzJob?) {
        if (quartzJob!!.id == 1L) {
            throw BadRequestException("该任务不可操作")
        }
        quartzManage.deleteJob(quartzJob)
        quartzJobRepository.delete(quartzJob)
    }

    @CacheEvict(allEntries = true)
    @Transactional(rollbackFor = [Exception::class])
    override fun delete(ids: Set<Long?>?) {
        for( id in ids!!){
            val quartzJob = findById(id)
            delete(quartzJob)
        }
    }
    override fun execution(quartzJob: QuartzJob?)  {
        quartzManage.runJobNow(quartzJob)
    }


    @Async
    @Transactional(rollbackFor = [java.lang.Exception::class])
    @Throws(InterruptedException::class)
    override fun executionSubJob(tasks: Array<String?>?) {
        for (id in tasks!!) {
            if (StrUtil.isBlank(id)) {
                // 如果是手动清除子任务id，会出现id为空字符串的问题
                continue
            }
            val quartzJob = findById(id!!.toLong())
            // 执行任务
            val uuid = IdUtil.simpleUUID()
            quartzJob!!.uuid = uuid
            // 执行任务
            execution(quartzJob)
            // 获取执行状态，如果执行失败则停止后面的子任务执行
            var result = redisUtils!!.get(uuid)
            while (result == null) {
                // 休眠5秒，再次获取子任务执行情况
                Thread.sleep(5000)
                result = redisUtils!!.get(uuid)
            }
            if (result!=null) {
                redisUtils!!.del(uuid)
                break
            }
        }
    }

    @Throws(IOException::class)
    override fun download(quartzJobs: MutableList<QuartzJob?>?, response: HttpServletResponse?) {
        val list: MutableList<Map<String?, Any?>?> = ArrayList()
        for (quartzJob in quartzJobs!!) {
            val map: MutableMap<String?, Any?> = LinkedHashMap()
            map["任务名称"] = quartzJob!!.jobName
            map["Bean名称"] = quartzJob.beanName
            map["执行方法"] = quartzJob.methodName
            map["参数"] = quartzJob.params
            map["表达式"] = quartzJob.cronExpression
            map["状态"] = if (quartzJob.isPause) "暂停中" else "运行中"
            map["描述"] = quartzJob.description
            map["创建日期"] = quartzJob.createTime
            list.add(map)
        }
        downloadExcel(list, response!!)
    }

    @Throws(IOException::class)
    override fun downloadLog(queryAllLog: MutableList<QuartzLog?>?, response: HttpServletResponse?) {
        val list: MutableList<Map<String?, Any?>?> = ArrayList()
        for (quartzLog in queryAllLog!!) {
            val map: MutableMap<String?, Any?> = LinkedHashMap()
            map["任务名称"] = quartzLog!!.jobName
            map["Bean名称"] = quartzLog.beanName
            map["执行方法"] = quartzLog.methodName
            map["参数"] = quartzLog.params
            map["表达式"] = quartzLog.cronExpression
            map["异常详情"] = quartzLog.exceptionDetail
            map["耗时/毫秒"] = quartzLog.time
            map["状态"] = if (quartzLog.isSuccess!!) "成功" else "失败"
            map["创建日期"] = quartzLog.createTime
            list.add(map)
        }
        downloadExcel(list, response!!)
    }
}